package com.example.springcloudredis.employees.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.springcloudredis.cache.LocalCache;
import com.example.springcloudredis.contants.RedisConstant;
import com.example.springcloudredis.employees.domain.Employees;
import com.example.springcloudredis.employees.service.EmployeesService;
import com.example.springcloudredis.employees.mapper.EmployeesMapper;
import com.google.common.cache.Cache;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.concurrent.TimeUnit;

/**
 *
 */
@Slf4j
@Service
public class EmployeesServiceImpl extends ServiceImpl<EmployeesMapper, Employees> implements EmployeesService {


    @Autowired
    private EmployeesMapper employeesMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private LocalCache localCache;

    /**
     * 基于数据库查询
     *
     * @param id
     * @return
     */
    @Override
    public Employees getEmployeesById(Integer id) {
        return employeesMapper.selectById(id);
    }

    /**
     * redis中如果能查到，先从redis中直接从redis中获取
     *
     * 500 * 500 随机 1~100用户ID
     * 3051 每秒TPS
     *
     * @param id
     * @return
     */
    @Override
    public Employees getEmployeesById1(Integer id) {

        Employees employees = null;
        Object o = redisTemplate.opsForValue().get(RedisConstant.EMPLOYEES_KEY + id);
        if (o != null) {
            log.info("从redis中获取数据,{}", id);
            return (Employees) o;
        }


        try {
            RLock lock = redissonClient.getLock("sys:lock:" + id);
            // lock()方法会尝试重新获取锁，所以此地需要使用了dubble check
            lock.lock();

            o = redisTemplate.opsForValue().get(RedisConstant.EMPLOYEES_KEY + id);
            if (o != null) {
                log.info("从redis中获取数据,{}", id);
                return (Employees) o;
            }


//            redisTemplate.opsForValue().setIfAbsent("sys:lock:" + id, "true", 30, TimeUnit.MILLISECONDS);

            employees = employeesMapper.selectById(id);
            log.info("数据库中获取数据,{}", id);
            if (employees != null) {
                redisTemplate.opsForValue().set(RedisConstant.EMPLOYEES_KEY + id, employees, 600, TimeUnit.SECONDS);
            }
        } catch (Exception e) {
            log.error("系统异常：{}", e);
        } finally {
            RLock lock = redissonClient.getLock("sys:lock:" + id);
            // 是否还是锁定状态
            if (lock.isLocked()) {
                // 为什么要判断是否当前线程时，才能解锁呢?
                // // TODO: 2021/7/12  在并发的情况下，很有可能当线程1拿到锁正在执行，线程2此时在排队等待锁被中断导致他直接执行了finally块，开始解锁
                // todo 但是由于锁不是线程2加的，就会抛出异常。
                // 时候是当前执行线程的锁
                if (lock.isHeldByCurrentThread()) {
                    lock.unlock(); // 释放锁
                }
            }

//            String o1 = (String) redisTemplate.opsForValue().get("sys:lock:" + id);
//            if (!StringUtils.isEmpty(o1)) {
//                redisTemplate.delete("sys:lock:" + id);
//            }
        }
        return employees;

    }


    /**
     * 说明增加的本地缓存确实解决了网络id问题
     * localCache + redis缓存 + 数据库 多级缓存架构
     * 500 * 500  1~100 随机用户id
     * 4264 每秒TPS
     * @param id
     * @return
     */
    @Override
    public Employees getEmployeesById2(Integer id) {
        Cache<String, Employees> cache = localCache.getCache();

        // 从本地缓存中获取数据
        Employees employees = cache.getIfPresent(RedisConstant.EMPLOYEES_KEY + id);
        if (employees != null) {
            log.info("从本地缓存中获取数据,{}", id);
            return employees;
        }

        // 从redis中获取
        Object o = redisTemplate.opsForValue().get(RedisConstant.EMPLOYEES_KEY + id);
        if (o != null) {
            log.info("从redis中获取数据,{}", id);
            employees = (Employees) o;
            cache.put(RedisConstant.EMPLOYEES_KEY + id, employees);
            return employees;
        }
        try {
            // 加分布式锁，防止集群并发问题
            RLock lock = redissonClient.getLock("sys:lock:" + id);
            lock.lock();

            // 双重校验
            employees = cache.getIfPresent(RedisConstant.EMPLOYEES_KEY + id);
            if (employees != null) {
                log.info("从本地缓存中获取数据,{}", id);
                return employees;
            }


            // 从数据库中获取
            employees = employeesMapper.selectById(id);

            if (employees != null) {
                log.info("从数据库中获取数据,{}", id);
                cache.put(RedisConstant.EMPLOYEES_KEY + id, employees);
                redisTemplate.opsForValue().set(RedisConstant.EMPLOYEES_KEY + id, employees);
                return employees;
            }

        } catch (Exception e) {
            log.error("系统异常，{}", e);
        } finally {
            RLock lock = redissonClient.getLock("sys:lock:" + id);
            if (lock.isLocked()) {
                if (lock.isHeldByCurrentThread()) {
                    lock.unlock();
                }
            }

        }

        return employees;
    }


}




